package com.henry.th;

import java.util.Arrays;

interface Buffer<T> {
    public boolean isEmpty();
    public boolean isFull();
    public T read();
    public void write(T objIn);
}

// Insert at the end and read from the front:

public class CircularBuffer<T> implements Buffer<T> {

    private T[] data;
    private int frontIdx;
    private int endIdx;

    CircularBuffer(T[] dataIn) {
        this.data = dataIn;
        this.frontIdx = 0;
        this.endIdx = 0;
    }

    CircularBuffer(T[] dataIn, int startIdxIn, int endIdxIn) {
        this.data = dataIn;
        this.frontIdx = startIdxIn;
        this.endIdx = endIdxIn;
    }

    private int getIncreasedIdx(int orgIdx) {
        orgIdx += 1;
        return orgIdx % data.length;
    }

    public boolean isFull() {
        return getIncreasedIdx(endIdx) == frontIdx;
    }

    public boolean isEmpty() {
        return frontIdx == endIdx;
    }

    public synchronized T read() {
        while (this.isEmpty()) {
            try {
                wait();
            } catch (InterruptedException ignored) { }
        }
        // now has contents to read
        T val = data[frontIdx];
        frontIdx = getIncreasedIdx(frontIdx);
        notifyAll();
        return val;
    }

    public synchronized void write(T objIn) {
        while (this.isFull()) {
            try {
                wait();
            } catch (InterruptedException ignored) { }
        }
        // now has space to put
        data[endIdx] = objIn;
        endIdx = getIncreasedIdx(endIdx);
        notifyAll();
    }

    @Override
    public String toString() {
        return "CircularBuffer{" +
                Arrays.toString(data) + " " +
                data.length + " " +
                frontIdx + " " +
                endIdx + "}";
    }
}

